package com.suntianci.stock.service.impl;

import com.alibaba.excel.EasyExcel;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.suntianci.stock.constant.StockConstant;
import com.suntianci.stock.mapper.*;
import com.suntianci.stock.pojo.domain.*;
import com.suntianci.stock.pojo.vo.StockInfoConfig;
import com.suntianci.stock.service.StockService;
import com.suntianci.stock.utils.DateTimeUtil;
import com.suntianci.stock.vo.resp.PageResult;
import com.suntianci.stock.vo.resp.R;
import com.suntianci.stock.vo.resp.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName StockServiceImpl
 * @Author 孙天赐
 * @Date 2024/12/26 14:37
 * @Description TODO: 股票相关服务层接口
 */
@Service
@Slf4j
public class StockServiceImpl implements StockService {
    @Autowired
    private StockInfoConfig stockInfoConfig;

    @Autowired
    private StockMarketIndexInfoMapper stockMarketIndexInfoMapper;

    @Autowired
    private StockBlockRtInfoMapper stockBlockRtInfoMapper;

    @Autowired
    private StockRtInfoMapper stockRtInfoMapper;

    @Autowired
    private Cache<String, Object> caffeineCache;

    @Autowired
    private StockOuterMarketIndexInfoMapper stockOuterMarketIndexInfoMapper;

    @Autowired
    private StockBusinessMapper stockBusinessMapper;

    /**
     * 获取国内大盘最新的数据
     *
     * @return
     */
    @Override
    public R<List<InnerMarketDomain>> getInnerMarketInfo() {
        // 默认从本地缓存加载数据，如果不存在则从数据库加载并同步到本地缓存
        // 在开盘周期内，本地缓存默认有效期1分钟
        R<List<InnerMarketDomain>> result = (R<List<InnerMarketDomain>>) caffeineCache.get("innerMarketKey", key -> {
            // 1. 获取股票最新的交易时间点（精确到分钟，秒和毫秒置为0）
            DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
            // 1.1转化为java可以识别的类型
            Date lastDate = lastDateTime.toDate();
            // 先使用假数据，等后续定时采集系统完成后，再删除假数据
//            lastDate = DateTime.parse("2022-07-07 14:52:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();
            // 2. 获取大盘编码集合
            List<String> mCodes = stockInfoConfig.getInner();
            // 3. 调用mapper查询数据
            List<InnerMarketDomain> list = stockMarketIndexInfoMapper.getMarketInfo(lastDate, mCodes);
            // 4. 封装并响应
            return R.ok(list);
        });
        return result;
    }

    /**
     * 获取国内板块最新的数据
     *
     * @return
     */
    @Override
    public R<List<StockBlockDomain>> getStockBlockInfo() {
        // 1. 获取最新交易时间点
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 1.1 转化为java可以识别的类型
        Date lastDate = lastDateTime.toDate();
        // 先使用假数据，等后续定时采集系统完成后，再删除假数据
//        lastDate = DateTime.parse("2022-01-14 16:57:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();
        // 2. 调用mapper查询数据
        List<StockBlockDomain> result = stockBlockRtInfoMapper.getblockInfo(lastDate);
        // 3. 封装并响应
        if (result.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(result);
    }

    /**
     * 分页查询最新股票数据
     *
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public R<PageResult<StockUpDownDomain>> getStockInfoByPage(Integer page, Integer pageSize) {
        // 1. 获取最新交易时间点
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 1.1 转化为java可以识别的类型
        Date lastDate = lastDateTime.toDate();
        // 先使用假数据，等后续采集系统完成之后，再删除假数据
//        lastDate = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();
        // 2. 设定分页参数 底层会拦截mybatis发送的sql，并动态追加limit语句实现分页
        PageHelper.startPage(page, pageSize);
        // 3. 调用mapper查询数据。由于设置了分页查询参数，这里的pageResult类型其实为Page
        List<StockUpDownDomain> result = stockRtInfoMapper.getStockInfo(lastDate);
        // 4.搭建PageResult对象
        // 4.1 将查询的Page对象封装到PageInfo下就可以获取分页的各种数据
        PageInfo<StockUpDownDomain> pageInfo = new PageInfo<>(result);
        // 4.2 将pageInfo对象封装到pageResult下（使用自己定义的构造函数实现的封装）
        // 因为PageInfo包含了丰富的分页信息，而部分分页信息是前端不需要的
        PageResult<StockUpDownDomain> pageResult = new PageResult<>(pageInfo);
        // 5. 响应数据
        return R.ok(pageResult);
    }

    /**
     * 查询涨幅最高前4条股票数据
     *
     * @return
     */
    @Override
    public R<List<StockUpDownDomain>> getBestStockInfo() {
        // 1.获取最新的时间
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 2.将时间转化为Java的Date类型
        Date lastDate = lastDateTime.toDate();
        // 使用假数据，等后续采集系统完成之后，再删除假数据
//        lastDate = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();
        // 调用mapper接口查询数据
        List<StockUpDownDomain> result = stockRtInfoMapper.getBestStockInfo(lastDate);
        // 响应数据
        if (result.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(result);
    }

    /**
     * 查询最新交易日内的涨跌停数量
     *
     * @return
     */
    @Override
    public R<Map<String, List<Map<String, Object>>>> getStockUpDownCount() {
        // 获取最新的交易日期
        DateTime endDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 使用假数据，等后续采集系统完成之后，再删除假数据
//        endDateTime = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        // 时间转化为Java的Date类型
        Date endDate = endDateTime.toDate();
        // 获取交易日期当天的开盘日期
        Date startDate = DateTimeUtil.getOpenDate(endDateTime).toDate();
        // 获取涨停的信息
        List<Map<String, Object>> upData = stockRtInfoMapper.getStockUpDownCount(startDate, endDate, 1);
        // 获取跌停的信息
        List<Map<String, Object>> downData = stockRtInfoMapper.getStockUpDownCount(startDate, endDate, 0);
        // 封装数据
        Map<String, List<Map<String, Object>>> result = new HashMap<>();
        result.put("upList", upData);
        result.put("downList", downData);
        // 响应数据
        return R.ok(result);
    }

    /**
     * 导出当前页股票数据
     *
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public void exportStockInfoByPage(Integer page, Integer pageSize, HttpServletResponse httpServletResponse) {
        // 1.获取分页数据
        // 1.1 获取最新交易时间点
        Date lastDate = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY)).toDate();
        // 1.2 先使用假数据，等后续采集系统完成之后，再删除假数据
//        lastDate = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toDate();
        // 1.3 设定分页参数 底层会拦截mybatis发送的sql，并动态追加limit语句实现分页
        PageHelper.startPage(page, pageSize);
        // 1.4 调用mapper查询数据。由于设置了分页查询参数，这里的pageResult类型其实为Page
        List<StockUpDownDomain> result = stockRtInfoMapper.getStockInfo(lastDate);
        // 1.5 如果数据为空，响应错误信息
        if (result.isEmpty()) {
            // 响应提示信息
            // 设置响应数据为json类型
            httpServletResponse.setContentType("application/json");
            // 设置响应数据的编码格式
            httpServletResponse.setCharacterEncoding("utf-8");
            R<Object> error = R.error(ResponseCode.NO_RESPONSE_DATA);
            try {
                // 将error转换为 JSON 格式的字符串
                String errorJson = new ObjectMapper().writeValueAsString(error);
                // 通过 HttpServletResponse 的输出流将结果响应给客户端。
                httpServletResponse.getWriter().write(errorJson);
            } catch (IOException e) {
                log.info("响应数据异常");
            }
            return;
        }

        // 2.将数据导出到excel中
        try {
            // 2.1 设置响应数据为excel文件格式类型
            httpServletResponse.setContentType("application/vnd.ms-excel");
            // 2.2 设置响应数据的编码格式
            httpServletResponse.setCharacterEncoding("utf-8");
            // 2.3 设置默认的文件名称
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("股票信息", "UTF-8");
            // 2.4 设置默认文件名称：兼容一些特殊浏览器
            httpServletResponse.setHeader("content-disposition", "attachment;filename=" + fileName + ".xlsx");
            // 2.5 响应excel流
            EasyExcel.write(httpServletResponse.getOutputStream(), StockUpDownDomain.class)
                    .sheet("股票信息")
                    .doWrite(result);
        } catch (IOException e) {
            e.printStackTrace();
            log.info("当前导出数据异常，当前页：{},每页大小：{},异常信息：{}", page, pageSize, e.getMessage());
        }
    }

    /**
     * 统计A股大盘T日和T-1日成交量对比
     *
     * @return
     */
    @Override
    public R<Map<String, List>> stockTradeAmoComparsion() {
        // 1.获取最新的交易日期
        DateTime lastEndDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 使用假数据，等后续采集系统完成之后，再删除假数据
//        lastEndDateTime = DateTime.parse("2022-07-07 14:52:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取最新交易日的开盘日期
        DateTime lastOpenDateTime = DateTimeUtil.getOpenDate(lastEndDateTime);
        // 将日期转化为Date类型
        Date lastOpenDate = lastOpenDateTime.toDate();
        Date lastEndDate = lastEndDateTime.toDate();
        // 2.获取上一个有效的交易日期
        DateTime preEndDateTime = DateTimeUtil.getPreviousTradingDay(lastEndDateTime);
        // 获取上一个有效的交易日期的开盘时间
        DateTime preOpenDateTime = DateTimeUtil.getOpenDate(preEndDateTime);
        // 转化为Date类型
        Date preEndDate = preEndDateTime.toDate();
        Date preOpenDate = preOpenDateTime.toDate();
        // 3.获取最新交易日期的成交量
        List<Map> lastAmoData = stockMarketIndexInfoMapper.getstockTradeAmo(lastOpenDate, lastEndDate, stockInfoConfig.getInner());
        // 4.获取前一交易日期的成交量
        List<Map> preAmoData = stockMarketIndexInfoMapper.getstockTradeAmo(preOpenDate, preEndDate, stockInfoConfig.getInner());
        // 5.组装数据
        Map<String, List> result = new HashMap<>();
        result.put("amtList", lastAmoData);
        result.put("yesAmtList", preAmoData);
        // 返回结果
        return R.ok(result);
    }

    /**
     * 统计个股（A股）分时涨跌幅度（根据涨跌幅划分区间）
     *
     * @return
     */
    @Override
    public R<Map> stockTimeSharingUpDown() {
        // 1.获取最新的交易日期
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 使用假数据，等后续采集系统完成之后，再删除假数据
//        lastDateTime = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        // 转化为Date类型
        Date lastDate = lastDateTime.toDate();
        // 2.获取并处理数据
        // 2.1 从数据库中获取数据
        List<Map> infos = stockRtInfoMapper.getstockTimeSharingUpDown(lastDate);
        // 2.2 获取有序的涨幅区间标题集合
        List<String> upDownRange = stockInfoConfig.getUpDownRange();
        // 2.3 根据标题集合，处理从数据库中得到的数据。补齐+排序
        // 方式一 使用循环
//        List<Map> finalInfos = new ArrayList<>();
//        for (String s : upDownRange) {
//            Map titleData = null;
//            for (Map info : infos) {
//                // 如果数据库中存在该区间数据，则不用补齐
//                if (info.containsValue(s)) {
//                    titleData = info;
//                    break;
//                }
//            }
//            // 如果数据库中不存在该区间数据，则用0补齐
//            if (titleData == null) {
//                titleData = new HashMap<String, Object>();
//                titleData.put("count", 0);
//                titleData.put("title", s);
//            }
//            finalInfos.add(titleData);
//        }
        // 方式二 使用stream流
        List<Map> finalInfos = upDownRange.stream().map(title -> {
            // 数据库中存在该区间数据
            Optional<Map> result = infos.stream().filter(map -> map.containsValue(title)).findFirst();
            // 如果存在
            if (result.isPresent()) {
                return result.get();
            } else {
                // 如果不存在
                HashMap<String, Object> tmp = new HashMap<>();
                tmp.put("count", 0);
                tmp.put("title", title);
                return tmp;
            }
        }).collect(Collectors.toList());
        // 3.封装数据
        Map<String, Object> result = new HashMap<>();
        // 获取指定日期格式的字符串
        String time = new DateTime(lastDate).toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        result.put("time", time);
        result.put("infos", finalInfos);
        // 4.返回结果
        return R.ok(result);
    }

    /**
     * 查询个股的分时行情数据，也就是统计指定股票T日每分钟的交易数据
     *
     * @param stockCode
     * @return
     */
    @Override
    public R<List<Stock4MinuteDomain>> stockDetailDataEveryMinute(String stockCode) {
        // 1.获取最新交易时间
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 先使用假数据，等后续采集系统完成之后，再删除假数据
//        lastDateTime = DateTime.parse("2022-01-07 14:58:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        // 获取开盘时间
        DateTime openDateTime = DateTimeUtil.getOpenDate(lastDateTime);
        // 转化为Date类型
        Date lastDate = lastDateTime.toDate();
        Date openDate = openDateTime.toDate();
        // 2.获取指定时间范围内的个股数据
        List<Stock4MinuteDomain> result = stockRtInfoMapper.getstockInfoInEveryMinute(openDate, lastDate, stockCode);
        // 封装并返回数据
        if (result.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(result);

    }

    /**
     * 获取个股日K线数据
     *
     * @param stockCode
     * @return
     */
    @Override
    public R<List<Stock4EveryDayDomain>> stockDayKLine(String stockCode) {
        // 1.获取数据的时间范围
        // 1.1 获取最新交易时间为截止时间
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY));
        // 使用假数据，等股票采集系统完成后，删除这段代码
//        lastDateTime = DateTime.parse("2022-01-07 14:26:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        // 1.2 获取前一个月的时间为开始时间
        DateTime startDateTime = lastDateTime.minusMonths(1);
        // 转化为Date类型
        Date lastDate = lastDateTime.toDate();
        Date startDate = startDateTime.toDate();
        // 2.查询日期范围内的每日最后的交易时间
        List<Date> timeList = stockRtInfoMapper.getEveryDayLastTime(startDate, lastDate, stockCode);
        // 3.根据每日最后的交易时间查询日K线数据
        List<Stock4EveryDayDomain> result = stockRtInfoMapper.getStockDayKLine(timeList, stockCode);
        // 4.响应结果
        if (result.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(result);
    }

    /**
     * 获取外盘指数行情数据，根据时间和大盘点数降序排序取前4条
     *
     * @return
     */
    @Override
    public R<List<OuterMarketDomain>> getOuterMarketInfo() {
        // 这里不需要根据时间查询，所以不需要获取时间
        // 调用mapper查询数据
        List<OuterMarketDomain> result = stockOuterMarketIndexInfoMapper.getOuterMarketInfo();
        if (result.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(result);
    }

    /**
     * 据输入的个股代码，进行模糊查询
     *
     * @param searchStr
     * @return
     */
    @Override
    public R<List<Map<String, String>>> searchStockByCode(String searchStr) {
        List<Map<String, String>> result = stockBusinessMapper.searchStockByCode(searchStr);
        return R.ok(result);
    }

    /**
     * 查询个股主营业务
     *
     * @param stockCode
     * @return
     */
    @Override
    public R<StockDescribeDataDomain> getStockMainBusiness(String stockCode) {
        StockDescribeDataDomain data = stockBusinessMapper.getStockMainBusiness(stockCode);
        if (data == null) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(data);
    }

    /**
     * 获取股票周k线数据
     *
     * @param stockCode
     * @return
     */
    @Override
    public R<List<Stock4EveryWeekDomain>> getStockEveryWeekInfo(String stockCode) {
        // 获取当前时间
        DateTime lastDateTime = DateTimeUtil.getLastDate4Stock(DateTime.now());
        // 根据当前时间查询半年前时间
        DateTime startDateTime = lastDateTime.minusMonths(6);
        // 转化为Date类型
        Date startDate = startDateTime.toDate();
        Date lastDate = lastDateTime.toDate();
        // 划分时间，获取每周的起始时间和终止时间
        List<Map<String, Date>> timeRange = stockRtInfoMapper.getEveryWeekStartAndEndTime(startDate, lastDate, stockCode);

        // 根据时间，查询每周的数据
        List<Stock4EveryWeekDomain> result = new ArrayList<>();
        for (Map<String, Date> time : timeRange) {
            Stock4EveryWeekDomain temp = stockRtInfoMapper.getStockWeekLine(time.get("minTime"), time.get("maxTime"), stockCode);
            if (temp != null) {
                result.add(temp);
            }
        }
        return R.ok(result);
    }

    /**
     * 获取个股最新分时行情数据
     * @param stockCode
     * @return
     */
    @Override
    public R<StockRtDomain> getStockRtInfo(String stockCode) {
        // 获取当前最新时间
        Date lastDate = DateTimeUtil.getLastDate4Stock(DateTime.now().minusSeconds(StockConstant.TIME_DELAY)).toDate();
        // 调用mapper获取数据
        StockRtDomain info = stockRtInfoMapper.getStockRtInfo(stockCode, lastDate);
        if(info == null){
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(info);
    }

    /**
     * 获取个股交易流水行情数据，取最新的前十条
     * @param stockCode
     * @return
     */
    @Override
    public R<List<SrockRtTransactionFlowDomain>> getSrockRtTransactionFlowInfo(String stockCode) {
        // 调用mapper获取数据
        List<SrockRtTransactionFlowDomain> info = stockRtInfoMapper.getSrockRtTransactionFlowInfo(stockCode);
        // 返回结果
        if (info.isEmpty()) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        return R.ok(info);
    }

}
